﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.TestServer
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.ServiceModel;
    using System.Threading;

    /// <summary>See <see cref="IFakeServer"/>.</summary>
    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    internal class FakeServer : IFakeServer
    {
        private static readonly Queue<Command> CommandQueue = new Queue<Command>();
        private static readonly ManualResetEvent QueueNonEmpty = new ManualResetEvent(false);
        private static Thread serverThread;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IFakeServer.Start"/>.</summary>
        public void Start(int port, byte[] greeting, bool disconnectAfterGreeting)
        {
            serverThread = new Thread(() => ThreadLoop(port, greeting, disconnectAfterGreeting));
            serverThread.Start();
        }

        /// <summary>See <see cref="IFakeServer.QueueCommand"/>.</summary>
        public void QueueCommand(byte[] expectedCommand, byte[] response)
        {
            lock (CommandQueue)
            {
                CommandQueue.Enqueue(new Command(expectedCommand, response));
                QueueNonEmpty.Set();
            }
        }

        /// <summary>See <see cref="IFakeServer.Stop"/>.</summary>
        public void Stop()
        {
            serverThread.Abort();
            serverThread.Join();
            serverThread = null;
            QueueNonEmpty.Reset();
            CommandQueue.Clear();
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String)", Justification = "Will not be localized.")]
        private static void ThreadLoop(int port, byte[] greeting, bool disconnectAfterGreeting)
        {
            try
            {
                TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), port);
                TcpClient tcpClient;

                try
                {
                    listener.Start();
                    Console.WriteLine("Fake Server Started.");

                    // Thread.Abort does not seem to work when we're inside and AcceptTcpClient() blocking call ...
                    while (!listener.Pending())
                    {
                        Thread.Sleep(300);
                    }

                    tcpClient = listener.AcceptTcpClient();
                }
                finally
                {
                    listener.Stop();
                }

                using (tcpClient)
                using (var stream = tcpClient.GetStream())
                {
                    if (greeting != null)
                    {
                        stream.Write(greeting, 0, greeting.Length);
                    }

                    if (disconnectAfterGreeting)
                    {
                        stream.Dispose();
                    }

                    while (true)
                    {
                        Command command = DequeueCommand();
                        byte[] actualCommand = new byte[command.ExpectedCommand.Length];
                        int offset = 0;

                        // If the actual command is shorter than the expected one, this loop ensures that the thread is
                        // blocking here and that the client will not receive a response
                        while ((offset += stream.Read(actualCommand, offset, actualCommand.Length - offset)) <
                            actualCommand.Length)
                        {
                        }

                        if (command.ExpectedCommand.SequenceEqual(actualCommand))
                        {
                            stream.Write(command.Response, 0, command.Response.Length);
                        }
                        else
                        {
                            // If the actual command does not match the expected command, breaking out of the loop (and thus
                            // terminating the thread) ensures that the client will not receive a response and time out.
                            break;
                        }

                        // If the actual command is longer than the expected command, we'll detect this when the next command is
                        // processed
                    }
                }
            }
            catch (ThreadAbortException)
            {
            }
            finally
            {
                Console.WriteLine("Fake Server Stopped.");
            }
        }

        private static Command DequeueCommand()
        {
            while (true)
            {
                QueueNonEmpty.WaitOne();

                lock (CommandQueue)
                {
                    if (CommandQueue.Count > 0)
                    {
                        return CommandQueue.Dequeue();
                    }
                    else
                    {
                        QueueNonEmpty.Reset();
                    }
                }
            }
        }

        /// <summary>Encapsulates an expected command and its response.</summary>
        private struct Command
        {
            internal readonly byte[] ExpectedCommand;
            internal readonly byte[] Response;

            internal Command(byte[] expectedCommand, byte[] response)
            {
                this.ExpectedCommand = expectedCommand;
                this.Response = response;
            }
        }
    }
}
